package com.lightboat.common.algorithm;

import com.alibaba.fastjson2.JSON;
import com.alibaba.fastjson2.TypeReference;
import com.lightboat.common.utils.StringUtils;
import lombok.extern.slf4j.Slf4j;
import org.springframework.data.redis.core.StringRedisTemplate;

import java.time.Duration;
import java.util.List;
import java.util.Set;
import java.util.concurrent.ThreadLocalRandom;

/**
 * 基于Redis的分布式抽取系统
 * 该类利用Redis存储抽取配置和权重信息，支持分布式环境下的高效抽取操作。
 */
@Slf4j
public class RedisLotteryService<T> {

    // Redis键前缀，用于区分不同活动的配置和权重信息
    private static final String LOTTERY_CONFIG_KEY = "lottery:config:";
    private static final String LOTTERY_WEIGHTS_KEY = "lottery:weights:";

    // Redis操作模板
    private StringRedisTemplate redisTemplate;

    /**
     * 构造函数，初始化Redis操作模板
     *
     * @param redisTemplate Redis操作模板实例
     */
    public RedisLotteryService(StringRedisTemplate redisTemplate) {
        this.redisTemplate = redisTemplate;
    }

    /**
     * 初始化抽取活动配置
     *
     * @param activityId 活动ID
     * @param prizes     抽取数据列表
     */
    public void initLotteryActivity(String activityId, List<WeightedLotteryWheel.Prize<T>> prizes) {
        activityId = StringUtils.isBlank(activityId) ? "all" : activityId;
        // 存储抽取数据配置到Redis
        String configKey = LOTTERY_CONFIG_KEY + activityId;
        redisTemplate.boundValueOps(configKey).set(JSON.toJSONString(prizes));

        // 构建权重区间并存储到Redis的有序集合中
        String weightsKey = LOTTERY_WEIGHTS_KEY + activityId;
        redisTemplate.delete(weightsKey);  // 清除旧数据

        int currentSum = 0;
        for (WeightedLotteryWheel.Prize<T> prize : prizes) {
            currentSum += prize.getWeight();
            redisTemplate.opsForZSet().add(weightsKey, prize.getId(), currentSum);
        }

        // 设置过期时间（可选）
        redisTemplate.expire(configKey, Duration.ofHours(24));  // 24小时
        redisTemplate.expire(weightsKey, Duration.ofHours(24));
    }

    /**
     * 执行抽取操作
     *
     * @param activityId 活动ID
     * @return 抽中的抽取数据信息
     * @throws RuntimeException 如果抽取失败或Redis操作异常
     */
    public WeightedLotteryWheel.Prize<String> drawPrize(String activityId) {
        activityId = StringUtils.isBlank(activityId) ? "all" : activityId;
        String weightsKey = LOTTERY_WEIGHTS_KEY + activityId;

        // 获取总权重
        Double maxWeight = redisTemplate.boundZSetOps(weightsKey).score(redisTemplate.boundZSetOps(weightsKey).reverseRange(0, 0).iterator().next());

        if (maxWeight == null || maxWeight == 0) {
            throw new RuntimeException("抽取活动不存在或已结束");
        }

        // 生成随机数
        double randomValue = ThreadLocalRandom.current().nextDouble(1, maxWeight + 1);

        log.warn("weightsKey:{},randomValue:{},maxWeight:{}", weightsKey, randomValue, maxWeight);

        // 使用Redis的ZRANGEBYSCORE查找第一个大于等于随机值的元素
        Set<String> result = redisTemplate.opsForZSet().rangeByScore(weightsKey, randomValue, maxWeight + 1, 0, 1);

        if (result != null && result.isEmpty()) {
            log.error("抽取异常,weightsKey:{},randomValue:{},maxWeight:{}", weightsKey, randomValue, maxWeight);
            throw new RuntimeException("抽取异常");
        }

        String prizeId = result.iterator().next();

        // 从配置中获取完整的抽取数据信息
        String configKey = LOTTERY_CONFIG_KEY + activityId;
        String configJson = redisTemplate.opsForValue().get(configKey);
        List<WeightedLotteryWheel.Prize<String>> prizes = JSON.parseObject(configJson, new TypeReference<List<WeightedLotteryWheel.Prize<String>>>() {});

        if (prizes != null) {
            return prizes.stream()
                    .filter(p -> p.getId().equals(prizeId))
                    .findFirst()
                    .orElseThrow(() -> new RuntimeException("抽取数据信息异常"));
        } else {
            throw new RuntimeException("抽取数据信息异常,抽取数据为空");
        }
    }
}
